#!/usr/bin/env python

# Standard library imports
import sys

import pygame
from pygame.locals import *
import pygame.sprite

import random

from numpy import zeros, linspace, short, fromstring, hstack, transpose
from numpy import array
from numpy.fft import fft

from exceptions import NotImplementedError

import swmixer


#init swmixer BEFORE pygame
swmixer.init(samplerate=11025, chunksize=1024, stereo=False, microphone=True)
#snd = swmixer.Sound("playalong.wav")
#snd.play(loops=-1)
import audiere
dev = audiere.open_device()
snd = dev.open_file("playback.mp3")
snd.play()

NUM_SAMPLES = 512
SAMPLING_RATE = 11025
SPECTRUM_UPDATE_SPEED = 100
freqs = []
frequencies = linspace(0., float(SAMPLING_RATE)/2, num=NUM_SAMPLES/2)
empty_amplitude = zeros(NUM_SAMPLES/2)

def get_audio_data():
    #swmixer.tick()
    audio_data = swmixer.get_microphone()
    normalized_data = audio_data / 32768.0
    return abs(fft(normalized_data))[:NUM_SAMPLES/2]

#other inits
SCREEN_WIDTH = 512
SCREEN_HEIGHT = 500


random.seed()
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))

SCROLL_SPEED = 5
FIRING_SPEED = 300
BOLTS_SPEED = 20
EXPLOSIONS_SPEED = 150
#percentage of chance to create a new enemy randomly
ENEMY_GENERATION = 5
#percentage of chance to create a new bonuss randomly
BONUS_GENERATION = 1.5

BAR_HEIGHT = 50

PLAYER_HEALTH_MAX = 255

ENEMIES_PNGS = ("teki1.png", "teki2.png")

#probabilities in percent of every bonuses to be selected when a bonus is spawned
bonusProbs = {"health":50, "death":5}

class Player(object):
    def __init__(self):
        self.health = PLAYER_HEALTH_MAX
        self.bonuses = []
    
    def isAlive(self):
        return self.health>=0
    
    def useBonuses(self):
        for bonus in self.bonuses:
            bonus.use(self)
            if bonus.nbUses<=0:
                self.bonuses.remove(bonus)
                bonus.kill()

class Bonus(pygame.sprite.Sprite):
    def __init__(self, pos, image):
        pygame.sprite.Sprite.__init__(self)
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.center = pos
        self.nbUses = 1
        
    def update(self):
        self.rect.move_ip((random.randint(-1,1),1))
        #out of screen tests
        if self.rect.left < 0:
            self.rect.left = 0
        elif self.rect.left > SCREEN_WIDTH:
            self.rect.left = SCREEN_WIDTH
        
        if self.rect.top < 0:
            self.rect.top = 0
        elif self.rect.bottom > SCREEN_HEIGHT-BAR_HEIGHT:
            self.kill()
    
    def use(self, moku):#abstract
        raise NotImplementedError()

class BonusHealth(Bonus):
    def __init__(self, pos):
        Bonus.__init__(self, pos, image=pygame.image.load("bonus_health.png").convert())
        
    def use(self, player):
        oldHealth = player.health
        player.health += 50
        player.health = min(player.health, PLAYER_HEALTH_MAX)
        print "%d health regained"%(oldHealth-player.health)
        self.nbUses-=1

class BonusDeath(Bonus):
    def __init__(self, pos):
        Bonus.__init__(self, pos, image=pygame.image.load("bonus_death.png").convert())
        
    def use(self, player):
        player.health =1
        print "player's health set to 1!!!"
        self.nbUses-=1

class BonusBomb(Bonus):
    def __init__(self, pos):
        Bonus.__init__(self, pos, image=pygame.image.load("bonus_bomb.png").convert())
    
    def use(self, player):
        bomb = pygame.sprite.Sprite()
        bomb.rect = self.rect.inflate(100, 100)
        collisions = pygame.sprite.spritecollide(bomb, enemiesGroup, True)
        nbEnemiesExploded = len(collisions)
        for col in collisions:
            explosionsGroup.add(Explosion(col.rect.center))
        if nbEnemiesExploded>0:
            print "%d enemies explosed!!!"%nbEnemiesExploded
        self.nbUses-=1

bonuses_types = [BonusBomb]#[BonusHealth, BonusDeath, BonusBomb]

class Enemy(pygame.sprite.Sprite):
    images = [pygame.image.load(imageFile).convert() for imageFile in ENEMIES_PNGS]
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = random.choice(self.images)
        self.rect = self.image.get_rect()
        self.rect.center = pos
        
    def update(self):
        self.rect.move_ip((random.randint(-1,1),1))
        
        #out of screen tests
        if self.rect.left < 0:
            self.rect.left = 0
        elif self.rect.left > SCREEN_WIDTH:
            self.rect.left = SCREEN_WIDTH
        
        if self.rect.top < 0:
            self.rect.top = 0
        elif self.rect.bottom > SCREEN_HEIGHT-BAR_HEIGHT:
            player.health-=10
            self.kill()

class Bolt(pygame.sprite.Sprite):
    image = pygame.image.load("bolt.png").convert()
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        #self.image = pygame.image.load("bolt.png").convert()
        self.rect = self.image.get_rect()
        self.rect.center = pos
        
    def update(self):
        self.rect.move_ip((0,-1))
        
        #out of screen tests
        if self.rect.top < 0:
            self.kill()

class Explosion(pygame.sprite.Sprite):
    images = [pygame.image.load("explosion%d.png"%d).convert() for d in xrange(3)]
    def __init__(self, pos):
        pygame.sprite.Sprite.__init__(self)
        self.image = self.images[0]
        self.rect = self.image.get_rect()
        self.rect.center = pos
        self.imageStep = 0
        self.maxSteps = 9
    def update(self):
        if self.imageStep == self.maxSteps:
            self.kill()
        else:
            self.imageStep+=1
            self.image = self.images[self.imageStep%len(self.images)]

#returns a Sprite or Null
def generateNewEnemy():
    if random.randint(0,100)<=ENEMY_GENERATION:
        enemy = Enemy((random.randint(0, SCREEN_WIDTH-32), 0))
        while pygame.sprite.spritecollideany(enemy, enemiesGroup) != None:
            enemy.rect.center = (random.randint(0, SCREEN_WIDTH-32), 0)
        return enemy
    else:
        return None

def generateNewBonus():
    if random.randint(0,100)<=BONUS_GENERATION:
        bonus = None
        type = random.choice(bonuses_types)
        if type == BonusHealth:
            bonus = BonusHealth((random.randint(0, SCREEN_WIDTH-32), 0))
        elif type == BonusDeath:
            bonus = BonusDeath((random.randint(0, SCREEN_WIDTH-32), 0))
        elif type == BonusBomb:
            bonus = BonusBomb((random.randint(0, SCREEN_WIDTH-32), 0))
        while pygame.sprite.spritecollideany(bonus, bonusesGroup) != None or pygame.sprite.spritecollideany(bonus, enemiesGroup):
            bonus.rect.center = (random.randint(0, SCREEN_WIDTH-32), 0)
        return bonus
    else:
        return None

bonusesGroup    = pygame.sprite.RenderPlain()
enemiesGroup    = pygame.sprite.RenderPlain()
boltsGroup      = pygame.sprite.RenderPlain()
explosionsGroup = pygame.sprite.RenderPlain()

def testCollisionEnemyBolt():
    collisions = pygame.sprite.groupcollide(enemiesGroup, boltsGroup, True, True)
    for col in collisions:
        explosionsGroup.add(Explosion(col.rect.center))

def testCollisionBonusBolt():
    collisions = pygame.sprite.groupcollide(bonusesGroup, boltsGroup, True, True)
    for col in collisions:
        player.bonuses.append(col)

clock = pygame.time.Clock()

pygame.time.set_timer(pygame.USEREVENT+1, SPECTRUM_UPDATE_SPEED)
pygame.time.set_timer(pygame.USEREVENT+2, SCROLL_SPEED)
pygame.time.set_timer(pygame.USEREVENT+3, FIRING_SPEED)
pygame.time.set_timer(pygame.USEREVENT+4, BOLTS_SPEED)
pygame.time.set_timer(pygame.USEREVENT+5, EXPLOSIONS_SPEED)
running = True

player = Player()

while running:
    clock.tick(30)
    swmixer.tick()
    
    screen.fill(pygame.color.Color("black"))
    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
               print "Abandon..."
               running = False
               break
            
        elif event.type == pygame.USEREVENT+1:
            freqs = get_audio_data()
            freqs = [freq if freqs[i]<BAR_HEIGHT else BAR_HEIGHT for i, freq in enumerate(freqs) ]
            #print "max: %f; min:%f"%(max(freqs), min(freqs))
        
        elif event.type == pygame.USEREVENT+2:
            #position update and random generation
            enemiesGroup.update()
            teki = generateNewEnemy()
            if teki!=None:
                enemiesGroup.add(teki)
            testCollisionEnemyBolt()
            
            bonusesGroup.update()
            bonus = generateNewBonus()
            if bonus!=None:
                bonusesGroup.add(bonus)
            testCollisionBonusBolt()
            player.useBonuses()
            
        elif event.type == pygame.USEREVENT+3:
            freqs[random.randint(0, 255)]=BAR_HEIGHT
            firingPos = [i for i, freq in enumerate(freqs) if freqs[i]==BAR_HEIGHT]
            for fp in firingPos:
                boltsGroup.add(Bolt((fp, SCREEN_HEIGHT-BAR_HEIGHT)))
        
        elif event.type == pygame.USEREVENT+4:
            boltsGroup.update()
            testCollisionEnemyBolt()
            
        elif event.type == pygame.USEREVENT+5:
            explosionsGroup.update()
        
        
    #drawing
    boltsGroup.draw(screen)
    enemiesGroup.draw(screen)
    bonusesGroup.draw(screen)
    explosionsGroup.draw(screen)
    i=0
    for pic in freqs:
        pygame.draw.line(screen, (255, 255, 255), (i,SCREEN_HEIGHT), (i,SCREEN_HEIGHT-pic), 1)
        i+=1
        pygame.draw.line(screen, (255, 255, 255), (i,SCREEN_HEIGHT), (i,SCREEN_HEIGHT-pic), 1)
        i+=1
    
    if not player.isAlive():
        print "gameover"
        break
    #the bar
    pygame.draw.line(screen, (255-player.health, player.health, 0), (0, SCREEN_HEIGHT-BAR_HEIGHT),
                         (SCREEN_WIDTH, SCREEN_HEIGHT-BAR_HEIGHT))
    
    pygame.display.flip()
    
print "fin"